using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;

using Leo.Core.Application.Contracts;
using Leo.Core.Domain;

using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Auditing;
using Volo.Abp.Domain.Entities;

namespace Leo.Core.Application
{
    public abstract class BaseCrudAppService<TEntity,
                                             TGetOutputDto,
                                             TGetListOutputDto,
                                             TPagedResultDto,
                                             TKey,
                                             TGetListInput,
                                             TCreateInput,
                                             TUpdateInput,
                                             TGetListPageInput>
                            : CrudAppService<TEntity,
                                             TGetOutputDto,
                                             TGetListOutputDto,
                                             TKey,
                                             TGetListInput,
                                             TCreateInput,
                                             TUpdateInput>
                       , IBaseCrudAppService<TGetOutputDto,
                                             TGetListOutputDto,
                                             TPagedResultDto,
                                             TKey,
                                             TGetListInput,
                                             TCreateInput,
                                             TUpdateInput,
                                             TGetListPageInput>
        where TKey : struct
        where TEntity : class, IEntity<TKey>
        where TGetOutputDto : IEntityDto<TKey>
        where TGetListOutputDto : IEntityDto<TKey>
        where TGetListPageInput : TGetListInput, ISortedResultRequest, IPagedResultRequest
        where TPagedResultDto : IPagedResult<TGetListOutputDto>, new()
    {
        protected BaseCrudAppService(
            IBaseAbpRepository<TEntity, TKey> mainRepository
        ) : base(mainRepository)
        {
        }

        protected Action<bool, string> CheckBusinessErrors = (predicate, code) =>
        {
            if (predicate)
            {
                var exception = new BusinessException(code);

                throw exception;
            }

        };

        protected Action<bool, string, Dictionary<string, object>> CheckBusinessErrorsWithData = (predicate, code, datas) =>
        {
            if (predicate)
            {
                var exception = new BusinessException(code);

                if (datas != null)
                {
                    foreach (var data in datas)
                        exception.WithData(data.Key, data.Value);
                }

                throw exception;
            }
        };

        protected virtual IQueryable<TEntity> BuildQuery(TGetListInput getListInput, IQueryable<TEntity> query)
        {
            return query;
        }

        public virtual async Task<List<TGetListOutputDto>> Query(TGetListInput getListInput)
        {
            var result = await this.GetListAsync(getListInput);

            return new List<TGetListOutputDto>(result.Items);
        }

        protected override async Task<IQueryable<TEntity>> CreateFilteredQueryAsync(TGetListInput input)
        {
            var query = await base.CreateFilteredQueryAsync(input);

            query = BuildQuery(input, query);

            return query;
        }

        public virtual async Task<long> QueryCount(TGetListInput getListInput)
        {
            var query = await CreateFilteredQueryAsync(getListInput);

            query = BuildQuery(getListInput, query);

            return query.LongCount();
        }

        public virtual async Task<TPagedResultDto> QueryByPage(TGetListPageInput getListPageInput)
        {
            var queryCount = await CreateFilteredQueryAsync(getListPageInput);
            var query = queryCount;

            if (!string.IsNullOrEmpty(getListPageInput.Sorting))
            {
                query = query.OrderBy(getListPageInput.Sorting);
            }
            else
            {
                if (typeof(IHasCreationTime).IsAssignableFrom(typeof(TEntity)))
                {
                    query = query.OrderByDescending(s => ((IHasCreationTime)s).CreationTime);
                }
            }

            query = query.Skip(getListPageInput.SkipCount);
            query = query.Take(getListPageInput.MaxResultCount);

            var dataCount = queryCount.LongCount();

            var dataList = await MapToGetListOutputDtosAsync(query.ToList());
            var result = new TPagedResultDto();
            result.TotalCount = dataCount;
            result.Items = dataList;
            return result;
        }
    }
}
